import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Desktop;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.Shape;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.io.IOException;
import java.io.Serializable;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;

import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JColorChooser;
import javax.swing.JDialog;
import javax.swing.JEditorPane;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSlider;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.ScrollPaneConstants;
import javax.swing.WindowConstants;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.HyperlinkEvent;
import javax.swing.event.HyperlinkListener;

public abstract class MapObject implements Serializable
{
    private static final long serialVersionUID = -7629866839012836656L;

    protected int xCoord;
    protected int yCoord;
    protected Map map;
    protected Color color;
    protected int transperancy;
    protected Shape icon;
    protected String information;
    protected String name;
    protected String files;
    protected ArrayList<Hyperlink> links;
    protected MapPanel containingPanel;
    protected boolean visibility;

    public MapObject(Map map, MapPanel containingPanel)
    {
	this.map = map;
	this.containingPanel = containingPanel;
	this.transperancy = 255; // change to half transparency, 255 is max
	this.information = "Enter Info Here";
	this.name = "Enter Name Here";
	this.visibility = true;
	this.links = new ArrayList<Hyperlink>();
    }

    public MapObject(MapObject mapObject)
    {
	this.xCoord = mapObject.xCoord;
	this.yCoord = mapObject.yCoord;
	this.map = mapObject.map;
	this.color = mapObject.color;
	this.transperancy = mapObject.transperancy;
	this.icon = mapObject.icon;
	this.information = mapObject.information;
	this.name = mapObject.name;
	this.files = mapObject.files;
	this.links = mapObject.links;
	this.containingPanel = mapObject.containingPanel;
	this.visibility = mapObject.visibility;
    }

    /**
     * Copies the MapObject
     * 
     * @return newObject - the new object
     */
    public abstract MapObject copy();

    /**
     * Sets the location of the MapObject to the given coords.
     * 
     * @param x
     *            - the x coord
     * @param y
     *            - the y coord
     */
    public abstract void setLoc(int x, int y);

    /**
     * Displays the creation prompt for the MapObject
     * 
     * @param isEditing
     *            - specifies whether the creation window should open in editing
     *            mode
     */
    public abstract void displayCreatePrompt(boolean isEditing);

    /**
     * Tells the MapObject to scale its location and size
     * 
     * @param scaleFactor
     *            - the amount to scale. 1.0 is normal scaling
     */
    public abstract void scaleCoords();

    /**
     * Transforms the MapObject into the object given in the parameters
     * 
     * @param object
     *            - the object to replace the old object
     */
    public void copyTo(MapObject object)
    {
	this.xCoord = object.xCoord;
	this.yCoord = object.yCoord;
	this.map = object.map;
	this.color = object.color;
	this.transperancy = object.transperancy;
	this.icon = object.icon;
	this.information = object.information;
	this.name = object.name;
	this.files = object.files;
	this.links = object.links;
	this.containingPanel = object.containingPanel;
    }

    public Map getMap()
    {
	return this.map;
    }

    public void displayViewPrompt()
    {
	JFrame infoWindow = new JFrame(this.name);
	infoWindow.setResizable(false);

	JPanel contentPanel = new JPanel();
	contentPanel.setPreferredSize(new Dimension(400, 300 + 20 * this.links.size()));
	contentPanel.setLayout(new GridBagLayout());

	JEditorPane objectInfo = new JEditorPane();
	objectInfo.setContentType("text/html");
	objectInfo.setText(this.information);
	objectInfo.setEditable(false);

	// objectInfo.setPreferredSize(new Dimension(400, 100));
	JScrollPane objectScrollArea = new JScrollPane();
	objectScrollArea.getViewport().add(objectInfo);
	objectScrollArea.setPreferredSize(new Dimension(400, 200));
	objectScrollArea.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
	objectScrollArea.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);

	JEditorPane hyperlinks = new JEditorPane();
	hyperlinks.setContentType("text/html");
	hyperlinks.setEditable(false);
	StringBuffer sb = new StringBuffer();
	for (Hyperlink link : MapObject.this.links)
	{
	    sb.append("<a href=\"" + link.getUrl() + "\">" + link.getTitle() + "</a><br>");
	}
	hyperlinks.setText(sb.toString());
	hyperlinks.addHyperlinkListener(new MapObjectHyperlinkListener());

	JButton ok = new JButton("Ok");
	ok.addActionListener(new OkListenter(infoWindow));

	GridBagConstraints constraints = new GridBagConstraints();
	constraints.fill = GridBagConstraints.HORIZONTAL;

	constraints.gridy = 0;
	constraints.weighty = 1;
	constraints.anchor = GridBagConstraints.LINE_START;
	contentPanel.add(objectScrollArea, constraints);

	constraints.gridy = 1;
	contentPanel.add(hyperlinks, constraints);

	constraints.gridy = 2;
	constraints.anchor = GridBagConstraints.PAGE_END;
	contentPanel.add(ok, constraints);

	infoWindow.add(contentPanel);
	infoWindow.pack();
	infoWindow.setLocationRelativeTo(null);
	infoWindow.setVisible(true);
    }

    public boolean isContained(int x, int y)
    {
	return this.icon.contains(x, y);
    }

    public void invertVisibility()
    {
	MapObject.this.visibility = !MapObject.this.visibility;

    }

    public void paint(Graphics g)
    {

	Graphics2D g2D = (Graphics2D) g;
	this.color = new Color(MapObject.this.color.getRed(), MapObject.this.color.getGreen(), MapObject.this.color.getBlue(), MapObject.this.transperancy);
	g2D.setColor(this.color);
	if (this.visibility)
	{
	    g2D.draw(this.icon);

	    g2D.fill(this.icon);
	}
    }

    @Override
    public String toString()
    {
	return this.name + ": " + this.information;
    }

    /***********
     ** Classes**
     ***********/

    protected class ObjectInfoWindowListener implements ActionListener
    {
	@Override
	public void actionPerformed(ActionEvent arg0)
	{
	    JDialog infoWindow = new JDialog((JFrame) null, MapObject.this.name, true);
	    infoWindow.setResizable(false);

	    JPanel contentPanel = new JPanel();
	    contentPanel.setPreferredSize(new Dimension(400, 300));
	    contentPanel.setLayout(new GridBagLayout());

	    JTextArea objectInfo = new JTextArea();
	    objectInfo.setLineWrap(true);
	    objectInfo.setWrapStyleWord(true);
	    objectInfo.setText(MapObject.this.information);
	    // constant value for the clear string since information will change
	    objectInfo.addFocusListener(new InstructorUI.ClearTextListener("Enter Info Here"));

	    JScrollPane objectScrollArea = new JScrollPane();
	    objectScrollArea.getViewport().add(objectInfo);
	    objectScrollArea.setPreferredSize(new Dimension(400, 200));
	    objectScrollArea.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
	    objectScrollArea.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);

	    JButton save = new JButton("Save");
	    save.addActionListener(new ObjectInfoSaveListener(infoWindow, objectInfo));

	    GridBagConstraints constraints = new GridBagConstraints();
	    constraints.fill = GridBagConstraints.HORIZONTAL;

	    constraints.gridy = 0;
	    constraints.weighty = 1;
	    constraints.anchor = GridBagConstraints.LINE_START;
	    contentPanel.add(objectScrollArea, constraints);

	    constraints.gridy = 1;
	    constraints.anchor = GridBagConstraints.PAGE_END;
	    contentPanel.add(save, constraints);

	    infoWindow.add(contentPanel);
	    infoWindow.pack();
	    save.requestFocusInWindow();
	    infoWindow.setLocationRelativeTo(null);
	    infoWindow.setVisible(true);
	}
    }

    private class ObjectInfoSaveListener implements ActionListener
    {
	private JDialog enclosingFrame;
	private JTextArea textArea;

	public ObjectInfoSaveListener(JDialog frame, JTextArea textArea)
	{
	    this.enclosingFrame = frame;
	    this.textArea = textArea;
	}

	@Override
	public void actionPerformed(ActionEvent arg0)
	{
	    MapObject.this.information = this.textArea.getText();
	    this.enclosingFrame.dispose();
	}
    }

    protected class MapObjectHyperlinkListener implements HyperlinkListener
    {

	@Override
	public void hyperlinkUpdate(HyperlinkEvent e)
	{
	    if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED)
	    {
		try
		{
		    // The description is the URL
		    // URI's revenge?
		    Desktop.getDesktop().browse(new URI(e.getDescription()));
		}
		catch (IOException e1)
		{
		    JOptionPane.showMessageDialog(null, "The given URL is not a valid URL\nThe URL was: " + e.getDescription(), "I Am So Broken...", JOptionPane.ERROR_MESSAGE);
		}
		catch (URISyntaxException e1)
		{
		    JOptionPane.showMessageDialog(null, "The given URL is not a valid URL\nThe URL was: " + e.getDescription(), "I Am So Broken...", JOptionPane.ERROR_MESSAGE);
		}
	    }
	}
    }

    protected class AddHyperlinkWindowListener implements ActionListener
    {
	@Override
	public void actionPerformed(ActionEvent e)
	{
	    JDialog frame = new JDialog((JFrame) null, "Add Hyperlink", true);
	    frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);

	    JTextField titleField = new JTextField("Enter the display name here");
	    titleField.addFocusListener(new InstructorUI.ClearTextListener(titleField.getText()));
	    JTextField urlField = new JTextField("Enter the url here");
	    urlField.addFocusListener(new InstructorUI.ClearTextListener(urlField.getText()));

	    JButton save = new JButton("Save");
	    save.addActionListener(new SaveHyperlinkListener(frame, titleField, urlField));

	    frame.add(titleField, BorderLayout.NORTH);
	    frame.add(urlField, BorderLayout.CENTER);
	    frame.add(save, BorderLayout.SOUTH);
	    
	    frame.setSize(new Dimension(250, 100));
	    frame.setLocationRelativeTo((Component) e.getSource());
	    frame.pack();
	    save.requestFocusInWindow();
	    frame.setVisible(true);
	}
    }

    private class SaveHyperlinkListener implements ActionListener
    {
	private JDialog frame;
	private JTextField titleBox;
	private JTextField urlBox;

	public SaveHyperlinkListener(JDialog frame, JTextField titleBox, JTextField urlBox)
	{
	    this.frame = frame;
	    this.titleBox = titleBox;
	    this.urlBox = urlBox;
	}

	@Override
	public void actionPerformed(ActionEvent arg0)
	{
	    String title = this.titleBox.getText();
	    String url = this.urlBox.getText();

	    MapObject.this.links.add(new Hyperlink(title, url));

	    this.frame.dispose();
	}
    }

    protected class RemoveHyperlinkWindowListener implements ActionListener
    {

	@Override
	public void actionPerformed(ActionEvent e)
	{
	     if (MapObject.this.links.size() <= 0)
	     {
		 return;
	     }

	     JDialog frame = new JDialog((JFrame) null, "Remove Hyperlinks", true);
	    frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);

	    GridLayout buttonGrid = new GridLayout(0, 1);
	    frame.setLayout(buttonGrid);

	    JCheckBox[] checkBoxButtons = new JCheckBox[MapObject.this.links.size()];
	    for (int i = 0; i < MapObject.this.links.size(); i++)
	    {
		checkBoxButtons[i] = new JCheckBox(MapObject.this.links.get(i).getTitle(), false);
		frame.add(checkBoxButtons[i]);
	    }

	    JButton apply = new JButton("Apply");
	    apply.addActionListener(new RemoveHyperlinkApplyListener(frame, checkBoxButtons));
	    frame.add(apply);

	    frame.pack();
	    frame.setLocationRelativeTo(null);
	    frame.setVisible(true);
	}

    }

    private class RemoveHyperlinkApplyListener implements ActionListener
    {
	private JCheckBox[] links;
	private JDialog frame;

	public RemoveHyperlinkApplyListener(JDialog frame, JCheckBox[] links)
	{
	    this.links = links;
	    this.frame = frame;
	}

	@Override
	public void actionPerformed(ActionEvent arg0)
	{
	    ArrayList<Integer> indices = new ArrayList<Integer>();

	    // gather the indices of the checked boxes checked boxes in
	    // reversed order
	    for (int i = this.links.length - 1; i >= 0; i--)
	    {
		if (this.links[i].isSelected())
		{
		    indices.add(i);
		}
	    }

	    // remove the link
	    for (int i : indices)
	    {
		MapObject.this.links.remove(i);
	    }
	    
	    this.frame.dispose();
	}

    }

    // save button action listener
    protected class CreatePromptSaveListener implements ActionListener
    {
	private JDialog enclosingFrame;
	private JTextArea nameArea;

	public CreatePromptSaveListener(JDialog frame, JTextArea nameArea)
	{
	    this.enclosingFrame = frame;
	    this.nameArea = nameArea;
	}

	@Override
	public void actionPerformed(ActionEvent arg0)
	{
	    MapObject.this.name = this.nameArea.getText();
	    this.enclosingFrame.dispose();
	}
    }

    protected class DeleteObjectListener implements ActionListener
    {
	private JDialog enclosingFrame;

	public DeleteObjectListener(JDialog frame)
	{
	    this.enclosingFrame = frame;
	}

	@Override
	public void actionPerformed(ActionEvent e)
	{
	    int choice = JOptionPane.showConfirmDialog(null, "Are you sure you want to delete the " + MapObject.this.getClass().getName() + "?", "Deletion Confirmation", JOptionPane.YES_NO_OPTION);
	    if (choice == JOptionPane.YES_OPTION)
	    {
		MapObject.this.containingPanel.deleteMapObject(MapObject.this.map, MapObject.this);
		MapObject.this.containingPanel.repaint();
		this.enclosingFrame.dispose();
	    }
	}
    }

    protected class OkListenter implements ActionListener
    {
	private JFrame frame;

	public OkListenter(JFrame frame)
	{
	    this.frame = frame;
	}

	@Override
	public void actionPerformed(ActionEvent arg0)
	{
	    this.frame.dispose();
	}
    }

    protected class ColorButtonListener implements ActionListener
    {
	@Override
	public void actionPerformed(ActionEvent arg0)
	{
	    Color selectedColor = JColorChooser.showDialog(null, "Pick A Color", MapObject.this.color);
	    if (selectedColor != null)
	    {
		// MapObject.this.color = JColorChooser.showDialog(null,
		// "Pick A Color", MapObject.this.color);
		MapObject.this.color = new Color(selectedColor.getRed(), selectedColor.getGreen(), selectedColor.getBlue(), MapObject.this.transperancy);
	    }
	}
    }

    protected class SliderTransperancyListener implements ChangeListener
    {
	@Override
	public void stateChanged(ChangeEvent e)
	{
	    MapObject.this.transperancy = ((JSlider) e.getSource()).getValue();
	    MapObject.this.color = new Color(MapObject.this.color.getRed(), MapObject.this.color.getGreen(), MapObject.this.color.getBlue(), MapObject.this.transperancy);
	}
    }

    protected class WindowCloseListerner implements WindowListener
    {

	@Override
	public void windowActivated(WindowEvent arg0)
	{	}

	@Override
	public void windowClosed(WindowEvent arg0)
	{	}

	@Override
	public void windowClosing(WindowEvent arg0)
	{
	    int choice = JOptionPane.showConfirmDialog(null, "Are you sure you want to cancel the operation?", "Cancel Confirmation", JOptionPane.YES_NO_OPTION);
	    if (choice == JOptionPane.YES_OPTION)
	    {
		((JDialog) arg0.getSource()).dispose();
		MapObject.this.containingPanel.setSystemState(SystemState.SELECT);
	    }
	}

	@Override
	public void windowDeactivated(WindowEvent arg0)
	{	}

	@Override
	public void windowDeiconified(WindowEvent arg0)
	{	}

	@Override
	public void windowIconified(WindowEvent arg0)
	{	}

	@Override
	public void windowOpened(WindowEvent arg0)
	{	}
    }
}
